/*
 * Copyright (c) Joachim Ansorg, mail@ansorg-it.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ansorgit.plugins.bash.jetbrains;

import com.intellij.execution.filters.Filter;
import com.intellij.execution.filters.HyperlinkInfo;
import com.intellij.execution.filters.InvalidExpressionException;
import com.intellij.execution.filters.OpenFileHyperlinkInfo;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Patched version taken from IntelliJ sources.
 * Changes:
 *  - fixed FILE_PATH_REGEX to allow more characters in file names
 *
 * @author Yura Cangea
 * @version 1.0
 */
public class ExtendedRegexFilter implements Filter {
    @NonNls public static final String FILE_PATH_MACROS = "$FILE_PATH$";
    @NonNls public static final String LINE_MACROS = "$LINE$";
    @NonNls public static final String COLUMN_MACROS = "$COLUMN$";

    // patched
    @NonNls private static final String FILE_PATH_REGEXP = "((?:\\p{Alpha}\\:)?[0-9 a-z_A-Z\\-\\\\./+#_$]+)";
    // /patched

    @NonNls private static final String NUMBER_REGEXP = "([0-9]+)";
    @NonNls private static final String FILE_STR = "file";
    @NonNls private static final String LINE_STR = "line";
    @NonNls private static final String COLUMN_STR = "column";

    private final int myFileRegister;
    private final int myLineRegister;
    private final int myColumnRegister;
    private final Pattern myPattern;
    private final Project myProject;

    public ExtendedRegexFilter(Project project, @NonNls String expression) {
        myProject = project;
        validate(expression);

        if (expression == null || expression.trim().isEmpty()) {
            throw new InvalidExpressionException("expression == null or empty");
        }

        int filePathIndex = expression.indexOf(FILE_PATH_MACROS);
        int lineIndex = expression.indexOf(LINE_MACROS);
        int columnIndex = expression.indexOf(COLUMN_MACROS);

        if (filePathIndex == -1) {
            throw new InvalidExpressionException("Expression must contain " + FILE_PATH_MACROS + " macros.");
        }

        final TreeMap<Integer,String> map = new TreeMap<Integer, String>();

        map.put(new Integer(filePathIndex), FILE_STR);

        expression = StringUtil.replace(expression, FILE_PATH_MACROS, FILE_PATH_REGEXP);

        if (lineIndex != -1) {
            expression = StringUtil.replace(expression, LINE_MACROS, NUMBER_REGEXP);
            map.put(new Integer(lineIndex), LINE_STR);
        }

        if (columnIndex != -1) {
            expression = StringUtil.replace(expression, COLUMN_MACROS, NUMBER_REGEXP);
            map.put(new Integer(columnIndex), COLUMN_STR);
        }

        // The block below determines the registers based on the sorted map.
        int count = 0;
        for (final Integer integer : map.keySet()) {
            count++;
            final String s = map.get(integer);

            if (FILE_STR.equals(s)) {
                filePathIndex = count;
            }
            else if (LINE_STR.equals(s)) {
                lineIndex = count;
            }
            else if (COLUMN_STR.equals(s)) {
                columnIndex = count;
            }
        }

        myFileRegister = filePathIndex;
        myLineRegister = lineIndex;
        myColumnRegister = columnIndex;
        myPattern = Pattern.compile(expression, Pattern.MULTILINE);
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    public static void validate(String expression) {
        if (expression == null || expression.trim().isEmpty()) {
            throw new InvalidExpressionException("expression == null or empty");
        }

        expression = substituteMacrosWithRegexps(expression);
        Pattern.compile(expression, Pattern.MULTILINE);
    }

    private static String substituteMacrosWithRegexps(String expression) {
        int filePathIndex = expression.indexOf(FILE_PATH_MACROS);
        int lineIndex = expression.indexOf(LINE_MACROS);
        int columnIndex = expression.indexOf(COLUMN_MACROS);

        if (filePathIndex == -1) {
            throw new InvalidExpressionException("Expression must contain " + FILE_PATH_MACROS + " macros.");
        }

        expression = StringUtil.replace(expression, FILE_PATH_MACROS, FILE_PATH_REGEXP);

        if (lineIndex != -1) {
            expression = StringUtil.replace(expression, LINE_MACROS, NUMBER_REGEXP);
        }

        if (columnIndex != -1) {
            expression = StringUtil.replace(expression, COLUMN_MACROS, NUMBER_REGEXP);
        }
        return expression;
    }

    @Override
    public Result applyFilter(String line, int entireLength) {
        Matcher matcher = myPattern.matcher(line);
        if (!matcher.find()) {
            return null;
        }

        String filePath = matcher.group(myFileRegister);
        if (filePath == null) {
            return null;
        }

        String lineNumber = "0";
        if (myLineRegister != -1) {
            lineNumber = matcher.group(myLineRegister);
        }

        String columnNumber = "0";
        if (myColumnRegister != -1) {
            columnNumber = matcher.group(myColumnRegister);
        }

        int line1 = 0;
        int column = 0;
        try {
            line1 = Integer.parseInt(lineNumber);
            column = Integer.parseInt(columnNumber);
        } catch (NumberFormatException e) {
            // Do nothing, so that line and column will remain at their initial zero values.
        }

        if (line1 > 0) line1 -= 1;
        if (column > 0) column -= 1;
        // Calculate the offsets relative to the entire text.
        final int highlightStartOffset = entireLength - line.length() + matcher.start(myFileRegister);
        final int highlightEndOffset = highlightStartOffset + filePath.length();
        final HyperlinkInfo info = createOpenFileHyperlink(filePath, line1, column);
        return new Result(highlightStartOffset, highlightEndOffset, info);
    }

    @Nullable
    protected HyperlinkInfo createOpenFileHyperlink(String fileName, final int line, final int column) {
        fileName = fileName.replace(File.separatorChar, '/');
        VirtualFile file = LocalFileSystem.getInstance().findFileByPath(fileName);
        return file != null ? new OpenFileHyperlinkInfo(myProject, file, line, column) : null;
    }

    public static String[] getMacrosName() {
        return new String[] {FILE_PATH_MACROS, LINE_MACROS, COLUMN_MACROS};
    }
}
